"""
天然气管网代理模型预测运行工具 - API 版本

本模块提供代理模型预测程序接口，支持：
1. 最大步数限制
2. 自动结果保存
3. 直接从 API 读取数据并进行处理
4. 生成代理模型所需的输入文件
5. 自动评估预测结果(MAPE、MSE、RMSE、MAE、R2)
6. 随机抽样节点评估（雷达图、柱状图）
7. 总体评估热力图
"""
import json
import os
import re
import traceback
from datetime import datetime, timedelta
from pathlib import Path
from typing import List, Union

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import requests
import torch
from pydantic import BaseModel

from .Predict import ModelPredictor, load_input_data_from_files
from config import settings
from utils.logger import create_logger
from utils.pressure import process_pressure_data

logger = create_logger(__name__)


# ==================== CSV/Excel 兼容性增强函数 ====================

def read_csv_compatible(file_path: Union[str, Path], **kwargs) -> pd.DataFrame:
    """
    兼容性增强的CSV读取函数，支持多种编码和分隔符自动检测

    Args:
        file_path: CSV文件路径
        **kwargs: 传递给pd.read_csv的其他参数

    Returns:
        pd.DataFrame: 读取的数据框

    Raises:
        FileNotFoundError: 文件不存在
        ValueError: 所有编码尝试都失败
    """
    file_path = Path(file_path)
    if not file_path.exists():
        raise FileNotFoundError(f"CSV文件不存在: {file_path}")

    # 定义要尝试的编码列表（优先级从高到低）
    encodings = ['utf-8-sig', 'utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1', 'iso-8859-1']

    # 定义要尝试的分隔符
    separators = kwargs.pop('sep', None)
    if separators is None:
        separators = [',', ';', '\t', '|']
    else:
        separators = [separators]

    last_error = None

    # 尝试不同的编码和分隔符组合
    for encoding in encodings:
        for sep in separators:
            try:
                df = pd.read_csv(
                    file_path,
                    encoding=encoding,
                    sep=sep,
                    on_bad_lines='skip',  # 跳过错误行
                    engine='python',  # 使用Python引擎以支持更灵活的解析
                    **kwargs
                )

                # 验证读取的数据是否有效（至少有1行1列）
                if not df.empty and len(df.columns) > 0:
                    logger.info(f"成功读取CSV文件 {file_path.name}，使用编码: {encoding}, 分隔符: {repr(sep)}")
                    return df

            except (UnicodeDecodeError, pd.errors.ParserError, pd.errors.EmptyDataError) as e:
                last_error = e
                continue
            except Exception as e:
                logger.warning(f"读取CSV文件时出现异常 (编码={encoding}, 分隔符={repr(sep)}): {e}")
                last_error = e
                continue

    # 所有尝试都失败
    error_msg = f"无法读取CSV文件 {file_path}，尝试了 {len(encodings)} 种编码和 {len(separators)} 种分隔符"
    if last_error:
        error_msg += f"，最后的错误: {str(last_error)}"
    logger.error(error_msg)
    raise ValueError(error_msg)


def read_excel_compatible(file_path: Union[str, Path], **kwargs) -> pd.DataFrame:
    """
    兼容性增强的Excel读取函数，支持多种引擎自动切换

    Args:
        file_path: Excel文件路径
        **kwargs: 传递给pd.read_excel的其他参数

    Returns:
        pd.DataFrame: 读取的数据框

    Raises:
        FileNotFoundError: 文件不存在
        ValueError: 所有引擎尝试都失败
    """
    file_path = Path(file_path)
    if not file_path.exists():
        raise FileNotFoundError(f"Excel文件不存在: {file_path}")

    # 根据文件扩展名确定要尝试的引擎
    file_ext = file_path.suffix.lower()
    if file_ext in ['.xlsx', '.xlsm']:
        engines = ['openpyxl', 'xlrd']
    elif file_ext in ['.xls']:
        engines = ['xlrd', 'openpyxl']
    else:
        engines = ['openpyxl', 'xlrd']

    last_error = None

    # 尝试不同的引擎
    for engine in engines:
        try:
            df = pd.read_excel(file_path, engine=engine, **kwargs)

            # 验证读取的数据是否有效
            if not df.empty and len(df.columns) > 0:
                logger.info(f"成功读取Excel文件 {file_path.name}，使用引擎: {engine}")
                return df

        except Exception as e:
            logger.warning(f"读取Excel文件时出现异常 (引擎={engine}): {e}")
            last_error = e
            continue

    # 所有尝试都失败
    error_msg = f"无法读取Excel文件 {file_path}，尝试了 {len(engines)} 种引擎"
    if last_error:
        error_msg += f"，最后的错误: {str(last_error)}"
    logger.error(error_msg)
    raise ValueError(error_msg)


def read_csv_with_open_compatible(file_path: Union[str, Path], mode='r') -> List[str]:
    """
    使用open()函数读取CSV文件的兼容性增强版本

    Args:
        file_path: CSV文件路径
        mode: 打开模式，默认为'r'

    Returns:
        List[str]: 文件的所有行

    Raises:
        FileNotFoundError: 文件不存在
        ValueError: 所有编码尝试都失败
    """
    file_path = Path(file_path)
    if not file_path.exists():
        raise FileNotFoundError(f"文件不存在: {file_path}")

    encodings = ['utf-8-sig', 'utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1', 'iso-8859-1']
    last_error = None

    for encoding in encodings:
        try:
            with open(file_path, mode, encoding=encoding, errors='replace') as f:
                lines = f.readlines()
                if lines:
                    return lines
        except (UnicodeDecodeError, IOError) as e:
            last_error = e
            continue

    error_msg = f"无法读取文件 {file_path}，尝试了 {len(encodings)} 种编码"
    if last_error:
        error_msg += f"，最后的错误: {str(last_error)}"
    logger.error(error_msg)
    raise ValueError(error_msg)

# ==================== 兼容性增强函数结束 ====================

##########################################从这里开始替换

# 设置中文字体支持
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans', 'Arial', 'Verdana']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['mathtext.fontset'] = 'cm'
plt.rcParams['font.size'] = 12

# 全局变量和常量定义
# 基础路径设置
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_ROOT = Path(__file__).parent.parent
# 初始状态文件路径
INITIAL_STATE_DIR = os.path.join(BASE_DIR, settings["filepath"]["initial_state_file_path"])
# 代理模型的输入文件路径
INPUT_FILES_DIR = os.path.join(BASE_DIR, settings["filepath"]["proxy_model_input_dir"])
# 模型文件路径
MODEL_PATH = os.path.join(BASE_DIR, settings["filepath"]["model_dir"], settings["filepath"]["model_filename"])
# API调用目录
API_DIR = os.path.join(BASE_DIR)

# 节点类型文件映射
NODE_FILES = {
    'yc': "after_processing_yc_data.csv",
    'B': "after_processing_B_data.csv",
    'E': "after_processing_E_data.csv",
    'KC': "after_processing_KC_data.csv",
    'NO': "after_processing_NO_data.csv",
    'RG': "after_processing_RG_data.csv",
    'RE': "after_processing_RE_data.csv",
    'T': "after_processing_T_data.csv",
    'H': "after_processing_H_data.csv"
}

# 初始状态文件映射
INITIAL_STATE_FILES = {
    'B': "B_data_initial_state.csv",
    'E': "E_data_initial_state.csv",
    'H': "H_data_initial_state.csv",
    'KC': "KC_data_initial_state.csv",
    'NO': "NO_data_initial_state.csv",
    'RE': "RE_data_initial_state.csv",
    'RG': "RG_data_initial_state.csv",
    'T': "T_data_initial_state.csv",
    'yc': "yc_initial_state.csv",
}

# 全局变量，用于在中断处理时保存当前状态
current_input_data = None
current_case_dir = None


# 定义请求体模型
class ProcessCSVRequest(BaseModel):
    headers: List[str]
    data: List[List[str]]

    # 可选：添加校验
    def validate_data(self):
        expected_len = len(self.headers)
        for i, row in enumerate(self.data):
            if len(row) != expected_len:
                raise ValueError(f"第 {i + 1} 行数据长度 {len(row)} 与 headers 长度 {expected_len} 不匹配")

# 注意：data_converter模块已不再使用，数据处理逻辑已集成到主程序中

def check_environment() -> bool:
    """检查工作环境是否正确"""
    errors = []

    if not os.path.exists(BASE_DIR):
        errors.append(f"错误：基础目录不存在: {BASE_DIR}")

    if not os.path.exists(INITIAL_STATE_DIR):
        errors.append(f"错误：初始状态文件目录不存在: {INITIAL_STATE_DIR}")

    if not os.path.exists(MODEL_PATH):
        errors.append(f"错误：模型文件不存在: {MODEL_PATH}")

    # 检查初始状态文件是否存在
    for file_type, file_name in INITIAL_STATE_FILES.items():
        file_path = os.path.join(INITIAL_STATE_DIR, file_name)
        if not os.path.exists(file_path):
            errors.append(f"错误：初始状态文件不存在: {file_path}")

    # 确保输入文件目录存在
    os.makedirs(INPUT_FILES_DIR, exist_ok=True)

    if errors:
        for error in errors:
            logger.info(error)
        return False
    return True


def create_save_directory() -> str:
    """创建保存目录并返回路径，直接使用predict_result目录"""
    save_dir = os.path.join(BASE_DIR, settings["filepath"]["predict_result_dir"])

    # 如果目录已存在，清空其中的内容
    if os.path.exists(save_dir):
        # 删除目录中的所有文件
        for file_name in os.listdir(save_dir):
            file_path = os.path.join(save_dir, file_name)
            if os.path.isfile(file_path):
                os.remove(file_path)
            elif os.path.isdir(file_path):
                import shutil
                shutil.rmtree(file_path)
        logger.info(f"已清空目录: {save_dir}")
    else:
        # 如果目录不存在，创建它
        os.makedirs(save_dir)
        logger.info(f"已创建目录: {save_dir}")

    # 同时创建/清空 zhangjiacheng 目录
    zhangjiacheng_dir = os.path.join(os.path.dirname(save_dir), "zhangjiacheng")
    if os.path.exists(zhangjiacheng_dir):
        # 删除目录中的所有文件
        for file_name in os.listdir(zhangjiacheng_dir):
            file_path = os.path.join(zhangjiacheng_dir, file_name)
            if os.path.isfile(file_path):
                os.remove(file_path)
            elif os.path.isdir(file_path):
                import shutil
                shutil.rmtree(file_path)
        logger.info(f"已清空目录: {zhangjiacheng_dir}")
    else:
        os.makedirs(zhangjiacheng_dir)
        logger.info(f"已创建目录: {zhangjiacheng_dir}")

    return save_dir


def generate_zhangjiacheng_data(predict_dir):
    """
    自动生成张家诚需要的处理后数据

    功能：
    1. 读取 predict_result 目录中的8个预测CSV文件
    2. 按1小时间隔筛选数据（每12行取1行，因为是5分钟间隔）
    3. 将时间调整为整点（去除分钟和秒）
    4. 截取 after_processing_yc_data.csv 的前3行（包括表头）
    5. 生成 station_pressure.csv（从NO_predictions.csv）
    6. 所有结果保存到 zhangjiacheng 文件夹中

    Args:
        predict_dir: 预测结果目录路径（predict_result）
    """
    try:
        # 定义输出目录（已在 create_save_directory 中创建）
        output_dir = os.path.join(os.path.dirname(predict_dir), "zhangjiacheng")

        # 定义需要处理的8个预测文件
        prediction_files = [
            "B_predictions.csv",
            "E_predictions.csv",
            "H_predictions.csv",
            "KC_predictions.csv",
            "NO_predictions.csv",
            "RE_predictions.csv",
            "RG_predictions.csv",
            "T_predictions.csv"
        ]

        # 任务1：处理8个预测文件（1小时间隔+整点）
        for file_name in prediction_files:
            try:
                input_path = os.path.join(predict_dir, file_name)
                output_path = os.path.join(output_dir, file_name)

                if not os.path.exists(input_path):
                    logger.warning(f"文件不存在，跳过: {file_name}")
                    continue

                # 读取CSV文件（使用兼容性增强版本）
                df = read_csv_compatible(input_path)
                time_column = df.columns[0]

                # 将第一列转换为datetime类型
                df[time_column] = pd.to_datetime(df[time_column])

                # 按1小时间隔筛选数据（每12行取1行）
                df_hourly = df.iloc[::12].copy()

                # 将时间调整为整点
                df_hourly[time_column] = df_hourly[time_column].apply(
                    lambda x: x.replace(minute=0, second=0, microsecond=0)
                )

                # 保存到新文件
                df_hourly.to_csv(output_path, index=False, encoding='utf-8-sig')
                logger.info(f"已处理: {file_name} ({len(df)} → {len(df_hourly)} 行)")

            except Exception as e:
                logger.error(f"处理文件 {file_name} 失败: {e}")

        # 任务2：截取 after_processing_yc_data.csv 的前3行
        try:
            input_files_dir = os.path.join(BASE_DIR, "data", "proxy_model_input")
            yc_input_path = os.path.join(input_files_dir, "after_processing_yc_data.csv")
            yc_output_path = os.path.join(output_dir, "after_processing_yc_data.csv")

            if os.path.exists(yc_input_path):
                yc_df = read_csv_compatible(yc_input_path)
                yc_df_head = yc_df.head(2)
                yc_df_head.to_csv(yc_output_path, index=False, encoding='utf-8-sig')
                logger.info(f"已处理: after_processing_yc_data.csv (前2行数据)")
            else:
                logger.warning("文件不存在: after_processing_yc_data.csv")
        except Exception as e:
            logger.error(f"处理 after_processing_yc_data.csv 失败: {e}")

        # 任务3：生成 station_pressure.csv
        try:
            reflect_dir = os.path.join(BASE_DIR, "data", "reflect_files")
            mapping_path = os.path.join(reflect_dir, "station_pressure_reflection.xlsx")
            no_csv_path = os.path.join(output_dir, "NO_predictions.csv")

            if os.path.exists(mapping_path) and os.path.exists(no_csv_path):
                df1 = read_excel_compatible(mapping_path)
                df2 = read_csv_compatible(no_csv_path)

                station_pressure_df = process_pressure_data(
                    df1, df2,
                    type_column="压力对比type值",
                    time_column="TIME"
                )

                station_pressure_path = os.path.join(output_dir, "station_pressure.csv")
                station_pressure_df.to_csv(station_pressure_path, index=False, encoding="utf-8-sig")
                logger.info(f"已生成: station_pressure.csv")
            else:
                if not os.path.exists(mapping_path):
                    logger.warning("映射文件不存在: station_pressure_reflection.xlsx")
                if not os.path.exists(no_csv_path):
                    logger.warning("NO预测文件不存在（在 zhangjiacheng 目录中）")
        except Exception as e:
            logger.error(f"生成 station_pressure.csv 失败: {e}")

        logger.info(f"zhangjiacheng 数据处理完成，输出目录: {output_dir}")

    except Exception as e:
        logger.error(f"generate_zhangjiacheng_data 执行失败: {e}")
        raise


# 新增函数：执行数据转换并获取转换后的数据
def convert_data_and_get_sample():
    """生成时间间隔数据，用于代理模型输入（当日00:00:00到23:55:00，每5分钟一个时间点）"""
    try:
        logger.info(f"生成时间间隔数据(当日整天，5分钟间隔)...")
        now = datetime.now()
        # 当天0点到当天结束
        start_time = datetime(now.year, now.month, now.day, 0, 0, 0)
        end_of_day = datetime(now.year, now.month, now.day, 23, 59, 59)

        time_intervals = []
        current_time = start_time
        while current_time <= end_of_day:
            time_intervals.append(current_time.strftime("%Y/%m/%d %H:%M:%S"))
            current_time += timedelta(minutes=5)

        logger.info(f"已生成 {len(time_intervals)} 个5分钟时间间隔，从{time_intervals[0]}到{time_intervals[-1]}")

        sample_data = {
            "time": time_intervals,
            "devices": {}
        }

        logger.info("时间间隔数据生成完成")
        return sample_data
    except Exception as e:
        logger.info(f"生成时间间隔数据时出错: {str(e)}")
        traceback.print_exc()
        return None


# 新增函数：读取站点-序号对应关系
def get_station_number_dict(mapping_file):
    """读取站点-序号对应关系"""
    try:
        df = read_excel_compatible(mapping_file)
        # 假设站点名称和序号分别在前两列
        station_number_dict = dict(zip(df.iloc[:, 0], df.iloc[:, 1]))
        logger.info("站点-序号对应关系示例：")
        # 打印前5个对应关系作为示例
        for k, v in list(station_number_dict.items())[:5]:
            logger.info(f"站点: {k} -> 序号: {v}")
        return station_number_dict
    except Exception as e:
        logger.info(f"读取站点-序号映射文件失败: {str(e)}")
        return {}


def clean_station_name(name):
    """处理站点名称，去掉最后一个_及其后面的内容"""
    # 去掉最后一个_及后面的内容
    name = name.rsplit('_', 1)[0] if '_' in name else name
    return name.strip()  # 去除可能的空格


def clean_station_name_for_new_conditions(name):
    """专门处理new_conditions.csv的站点名称，去掉冒号及其后面的内容"""
    # 如果存在冒号，则去掉冒号及其后面的内容
    if ':' in name:
        name = name.split(':', 1)[0]
    return name.strip()


def ensure_complete_data(file_data, initial_header, initial_second_row, initial_third_row, station_number_dict,
                         time_column):
    """
    确保所有列都有对应的序号和初始状态数据

    Args:
        file_data: 文件数据字典
        initial_header: 初始状态文件的表头
        initial_second_row: 初始状态文件的第二行（序号）
        initial_third_row: 初始状态文件的第三行（初始状态数据）
        station_number_dict: 站点-序号映射字典
        time_column: 时间列
    """
    # 检查是否有缺失的列
    missing_columns = [col for col in initial_header if col not in file_data['header'] and col.strip()]

    if missing_columns:
        logger.info(f"发现 {len(missing_columns)} 个缺失的列，使用初始状态数据补充")

        for col in missing_columns:
            if col in initial_header:
                # 检查是否为NO类型的数据，需要特殊处理
                is_no_data = False
                for file_type, file_name in NODE_FILES.items():
                    if file_name == "after_processing_NO_data.csv" and file_type == 'NO':
                        is_no_data = True
                        break

                # 如果是NO类型的数据，检查是否在NO_data_initial_state.csv中有匹配的列名
                if is_no_data:
                    # 检查NO_data_initial_state.csv文件是否存在
                    no_data_initial_state_path = os.path.join(INITIAL_STATE_DIR, "NO_data_initial_state.csv")
                    if os.path.exists(no_data_initial_state_path):
                        try:
                            # 读取NO_data_initial_state.csv文件的第一行（列名）
                            lines = read_csv_with_open_compatible(no_data_initial_state_path)
                            if lines:
                                header_line = lines[0].strip()
                                logger.info(f"NO列名实例: {header_line[:5]}")
                                headers = [col_name.strip() for col_name in header_line.split(',')]
                                #logger.info(f"NO列名实例: {headers[:5]}")

                            # 检查列名是否在NO_data_initial_state.csv中
                            if col not in headers:
                                logger.info(f"  - 列名 {col} 不在NO_data_initial_state.csv中，跳过")
                                continue
                        except Exception as e:
                            logger.info(f"读取NO_data_initial_state.csv文件时出错: {str(e)}")

                col_idx = initial_header.index(col)
                if col_idx < len(initial_third_row):
                    # 添加到表头
                    file_data['header'].append(col)

                    # 获取初始值
                    initial_value = initial_third_row[col_idx]

                    # 复制初始值到所有时间步
                    try:
                        initial_value_float = float(initial_value)
                        # 获取时间步数
                        time_steps = len(time_column)
                        file_data['data'].append([initial_value_float] * time_steps)
                        logger.info(f"  - 补充列 {col}: 初始值 {initial_value_float}")
                    except ValueError:
                        # 如果无法转换为浮点数，使用原始字符串
                        time_steps = len(time_column)
                        file_data['data'].append([initial_value] * time_steps)
                        logger.info(f"  - 补充列 {col}: 初始值 {initial_value}")

    # 检查是否有需要添加后缀的列
    # 尝试为每个initial_header中的列名添加:SNQ或_q后缀，检查是否匹配flow_data中的键
    global global_flow_data
    if global_flow_data:
        for col in initial_header:
            # 尝试添加:SNQ后缀
            col_snq = f"{col}:SNQ"
            if col_snq in global_flow_data and col_snq not in file_data['header']:
                # 添加到表头
                file_data['header'].append(col_snq)
                # 获取flow_data中的值
                value = global_flow_data[col_snq]
                # 复制值到所有时间步
                time_steps = len(time_column)
                file_data['data'].append([value] * time_steps)
                logger.info(f"  - 添加带:SNQ后缀的列 {col_snq}: 值 {value}")

            # 尝试添加_q后缀
            col_q = f"{col}_q"
            if col_q in global_flow_data and col_q not in file_data['header']:
                # 添加到表头
                file_data['header'].append(col_q)
                # 获取flow_data中的值
                value = global_flow_data[col_q]
                # 复制值到所有时间步
                time_steps = len(time_column)
                file_data['data'].append([value] * time_steps)
                logger.info(f"  - 添加带_q后缀的列 {col_q}: 值 {value}")


def get_initial_value_for_column(col_name, initial_header, initial_third_row):
    """
    获取指定列的初始状态值

    Args:
        col_name: 列名
        initial_header: 初始状态文件的表头
        initial_third_row: 初始状态文件的第三行（初始状态数据）

    Returns:
        float or str: 初始状态值
    """
    if col_name in initial_header:
        col_idx = initial_header.index(col_name)
        if col_idx < len(initial_third_row):
            initial_value = initial_third_row[col_idx]
            try:
                return float(initial_value)
            except ValueError:
                return initial_value
    return 0.0


# 处理输入数据的函数，为代理模型准备CSV文件
def process_input_data_for_proxy_model(input_data, initial_state_dir, output_dir):
    """
    处理输入数据，生成代理模型所需的CSV文件

    根据用户要求：
    1. 从flow_hour_url和sensor_url读取数据
    2. 根据映射关系Excel文件进行分类
    3. 正确处理缺失数据，使用初始状态文件补充
    4. 添加站点-序号对应关系

    Args:
        input_data: 输入数据字典
        initial_state_dir: 初始状态文件目录
        output_dir: 输出文件目录
    """
    if input_data is None:
        logger.info("未提供输入数据，将自动调用数据转换模块获取数据")
        input_data = convert_data_and_get_sample()
    logger.info("处理输入数据，为代理模型准备CSV文件...")

    # 确保输出目录存在
    os.makedirs(output_dir, exist_ok=True)

    # 尝试读取站点-序号映射文件
    station_number_dict = {}

    # 获取站点-序号映射文件路径
    node_id_all_file_path = PROJECT_ROOT / settings["filepath"]["node_id_all_file_path"]
    logger.info(f"站点-序号映射文件路径: {node_id_all_file_path}")
    mapping_file_paths = [node_id_all_file_path,
                          os.path.join(BASE_DIR, node_id_all_file_path),
                          os.path.join(os.path.dirname(BASE_DIR), node_id_all_file_path)
                          ]
    logger.info(f"尝试读取站点-序号映射文件: {mapping_file_paths}")

    for mapping_file in mapping_file_paths:
        if os.path.exists(mapping_file):
            logger.info(f"找到站点-序号映射文件: {mapping_file}")
            station_number_dict = get_station_number_dict(mapping_file)
            logger.info(f"成功读取站点-序号映射文件: {mapping_file}")
            break

    if not station_number_dict:
        logger.info("警告: 未找到站点-序号映射文件，将使用空序号")

    # 获取时间列和设备数据
    time_column = input_data.get("time", [])
    devices = input_data.get("devices", {})

    # 获取算例编号
    save_dir = os.path.join(BASE_DIR, settings["filepath"]["predict_result_dir"])
    existing_cases = [d for d in os.listdir(save_dir) if d.startswith('第') and d.endswith('个算例')]
    case_number = max([int(case[1:-3]) for case in existing_cases], default=0)

    # 初始化各类型数据字典
    data_files = {
        'yc': {'header': [], 'data': []},
        'B': {'header': [], 'data': []},
        'E': {'header': [], 'data': []},
        'H': {'header': [], 'data': []},
        'KC': {'header': [], 'data': []},
        'NO': {'header': [], 'data': []},
        'RE': {'header': [], 'data': []},
        'RG': {'header': [], 'data': []},
        'T': {'header': [], 'data': []}
    }

    # 加载初始状态文件的表头和第二行数据
    initial_headers = {}
    initial_second_rows = {}
    initial_third_rows = {}

    for file_type in data_files.keys():
        file_name = INITIAL_STATE_FILES[file_type]
        file_path = os.path.join(initial_state_dir, file_name)

        if os.path.exists(file_path):
            try:
                # 使用兼容性增强版本读取文件
                lines = read_csv_with_open_compatible(file_path)
                if len(lines) >= 3:  # 确保至少有3行
                    # 第一行是表头
                    header_line = lines[0].strip()
                    initial_headers[file_type] = [col.strip() for col in header_line.split(',')]

                    # 第二行是序号
                    second_row = lines[1].strip()
                    initial_second_rows[file_type] = [col.strip() for col in second_row.split(',')]

                    # 第三行是初始状态数据
                    third_row = lines[2].strip()
                    initial_third_rows[file_type] = [col.strip() for col in third_row.split(',')]
            except Exception as e:
                logger.info(f"读取初始状态文件 {file_name} 时出错: {str(e)}")

        # 读取映射关系Excel文件
    mapping_df = None
    try:
        mapping_file = PROJECT_ROOT / settings["filepath"]["real_data_reflection_file_path"]
        mapping_df = read_excel_compatible(mapping_file)
        logger.info(f"成功读取实际数据映射关系文件: {mapping_file}")
    except Exception as e:
        logger.info(f"读取实际数据映射关系文件失败: {str(e)}")
        mapping_df = None

    # 读取input_node_feature.xlsx文件
    input_node_feature_df = None
    try:
        input_node_feature_file = PROJECT_ROOT / settings["filepath"]["input_node_feature_file_path"]
        input_node_feature_df = read_excel_compatible(input_node_feature_file)
        logger.info(f"成功读取input_node_feature文件: {input_node_feature_file}")
    except Exception as e:
        logger.info(f"读取input_node_feature文件失败: {str(e)}")
        input_node_feature_df = None

    # 处理flow_hour_url数据
    flow_data = process_flow_hour_data()

    # 处理sensor_url数据 - 获取所有opcAddress和对应的currValue（强制从URL获取并缓存）
    sensor_data = process_sensor_data(force_refresh=True, time_column=time_column)

    # 保存flow_data到全局变量，供ensure_complete_data函数使用
    global global_flow_data
    global_flow_data = flow_data

    # 处理flow_hour数据 - 根据所有initial_state文件进行分类
    if flow_data:
        logger.info(f"开始将flow_hour数据填充到对应的after_processing文件中...")
        for node_name, value in flow_data.items():
            # 检查节点名称是否已经包含后缀（:SNQ或_q）
            has_suffix = ":SNQ" in node_name or "_q" in node_name

            # 遍历所有文件类型
            matched = False
            for file_type, headers in initial_headers.items():
                # 检查节点名称是否在当前文件的表头中
                if node_name in headers:
                    if node_name not in data_files[file_type]['header']:
                        data_files[file_type]['header'].append(node_name)
                        # 对所有时间点使用相同的值
                        data_files[file_type]['data'].append([value] * len(time_column))
                        logger.info(f"  - 将节点 {node_name} 填充到 {file_type} 文件中")
                        matched = True

            # 如果没有匹配到任何文件，记录日志
            if not matched and not has_suffix:
                logger.info(f"  - 节点 {node_name} 未能匹配到任何initial_state文件")

        logger.info(f"flow_hour数据填充完成")

    # 处理sensor数据 - 根据input_node_feature.xlsx文件和real_data_reflection.xlsx文件进行分类
    if input_node_feature_df is not None and sensor_data:
        # 获取NO_data文件的列名列表，用于匹配节点特征组合名称
        no_data_headers = initial_headers.get('NO', [])

        # 为每个节点选择最优的opcAddress数据
        node_feature_data = select_optimal_sensor_data_from_input_feature(input_node_feature_df, sensor_data,
                                                                          no_data_headers)

        # 将选择的数据分配到对应的文件中
        for combined_name, curr_value in node_feature_data.items():
            # 确定应该放在哪个文件中
            target_file = None
            for file_type, headers in initial_headers.items():
                if combined_name in headers:
                    target_file = file_type
                    break

            # 如果没有找到匹配的文件，根据节点名称前缀判断
            if target_file is None:
                # 提取节点名称，同时考虑冒号和下划线两种格式
                if ':' in combined_name:
                    node_name = combined_name.split(':')[0]
                elif '_' in combined_name:
                    node_name = combined_name.split('_')[0]
                else:
                    node_name = combined_name

                for file_type in data_files.keys():
                    if node_name.startswith(file_type.upper()) or node_name.startswith(file_type):
                        target_file = file_type
                        break

            # 如果仍然没有找到匹配的文件，检查是否在NO_data_initial_state.csv中有匹配的列名
            if target_file is None:
                # 检查NO_data_initial_state.csv文件是否存在
                no_data_initial_state_path = os.path.join(INITIAL_STATE_DIR, "NO_data_initial_state.csv")
                if os.path.exists(no_data_initial_state_path):
                    try:
                        # 读取NO_data_initial_state.csv文件的第一行（列名）
                        lines = read_csv_with_open_compatible(no_data_initial_state_path)
                        if lines:
                            header_line = lines[0].strip()
                            headers = [col_name.strip() for col_name in header_line.split(',')]

                        # 检查列名是否在NO_data_initial_state.csv中
                        if combined_name in headers:
                            logger.info(
                                f"节点特征组合名称 {combined_name} 在NO_data_initial_state.csv中找到，分配到NO文件")
                            target_file = 'NO'
                        else:
                            logger.info(f"节点特征组合名称 {combined_name} 不在任何initial_state文件中找到，跳过")
                            continue
                    except Exception as e:
                        # logger.info(f"读取NO_data_initial_state.csv文件时出错: {str(e)}")
                        continue
                else:
                    logger.info(f"节点特征组合名称 {combined_name} 不在任何initial_state文件中找到，跳过")
                    continue

            # 添加列名到对应文件的表头
            if combined_name not in data_files[target_file]['header']:
                data_files[target_file]['header'].append(combined_name)
                # 准备该列的时间序列
                if isinstance(curr_value, (list, tuple, np.ndarray)):
                    values_list = [float(v) if v is not None else 0.0 for v in curr_value]
                else:
                    values_list = [float(curr_value)] * len(time_column)

                # 对齐长度（截断或填充初始值）
                if len(values_list) < len(time_column):
                    # 获取初始值用于填充
                    init_headers = initial_headers.get(target_file, [])
                    init_third = initial_third_rows.get(target_file, [])
                    initial_val = get_initial_value_for_column(combined_name, init_headers, init_third)
                    try:
                        initial_val = float(initial_val)
                    except Exception:
                        initial_val = 0.0
                    pad_len = len(time_column) - len(values_list)
                    values_list = values_list + [initial_val] * pad_len
                elif len(values_list) > len(time_column):
                    values_list = values_list[:len(time_column)]

                data_files[target_file]['data'].append(values_list)
                # logger.info(f"将节点特征组合名称 {combined_name} 添加到 {target_file} 文件中，长度对齐为 {len(values_list)}")

    # 处理每个文件类型，补充缺失的列
    for file_type, file_data in data_files.items():
        # 获取初始状态文件的表头
        initial_header = initial_headers.get(file_type, [])
        initial_second_row = initial_second_rows.get(file_type, [])
        initial_third_row = initial_third_rows.get(file_type, [])

        # 如果初始状态文件存在
        if initial_header:
            # 找出当前数据中缺失的列
            missing_columns = [col for col in initial_header if col not in file_data['header'] and col.strip()]

            # 如果有缺失的列，从初始状态文件中读取数据
            if missing_columns:
                logger.info(f"文件类型 {file_type} 缺失 {len(missing_columns)} 列，使用初始状态数据补充")
                # 为每个缺失的列添加数据
                for col in missing_columns:
                    if col in initial_header:
                        col_idx = initial_header.index(col)
                        if col_idx < len(initial_third_row):
                            # 添加到表头
                            file_data['header'].append(col)

                            # 获取初始值
                            initial_value = initial_third_row[col_idx]

                            # 检查是否为ST特征（压缩机启停）
                            is_st_feature = False
                            if ':ST' in col or '_st' in col.lower():
                                is_st_feature = True
                                # logger.info(f"检测到ST特征: {col}")

                            # 复制初始值到所有时间步
                            try:
                                initial_value_float = float(initial_value)

                                # 对ST特征进行特殊处理
                                if is_st_feature:
                                    processed_value = 1.0 if initial_value_float >= 1000 else 0.0
                                    file_data['data'].append([processed_value] * len(time_column))
                                    # logger.info(f"  - 补充ST特征列 {col}: 初始值 {initial_value_float}, 处理后: {processed_value}")
                                else:
                                    file_data['data'].append([initial_value_float] * len(time_column))
                                    # logger.info(f"  - 补充列 {col}: 初始值 {initial_value_float}")
                            except ValueError:
                                # 如果无法转换为浮点数，使用原始字符串
                                file_data['data'].append([initial_value] * len(time_column))
                                # logger.info(f"  - 补充列 {col}: 初始值 {initial_value}")

            # 如果当前文件类型完全没有数据，使用初始状态文件的所有数据
            elif not file_data['header']:
                logger.info(f"文件类型 {file_type} 没有传输数据，使用初始状态文件的所有数据")
                for col_idx, col in enumerate(initial_header):
                    if col.strip():  # 跳过空列名
                        file_data['header'].append(col)

                        # 获取初始值
                        if col_idx < len(initial_third_row):
                            initial_value = initial_third_row[col_idx]

                            # 检查是否为ST特征（压缩机启停）
                            is_st_feature = False
                            if ':ST' in col or '_st' in col.lower():
                                is_st_feature = True
                                logger.info(f"检测到ST特征: {col}")

                            # 复制初始值到所有时间步
                            try:
                                initial_value_float = float(initial_value)

                                # 对ST特征进行特殊处理
                                if is_st_feature:
                                    processed_value = 1.0 if initial_value_float >= 1000 else 0.0
                                    file_data['data'].append([processed_value] * len(time_column))
                                    logger.info(
                                        f"  - 处理ST特征列 {col}: 初始值 {initial_value_float}, 处理后: {processed_value}")
                                else:
                                    file_data['data'].append([initial_value_float] * len(time_column))
                            except ValueError:
                                # 如果无法转换为浮点数，使用原始字符串
                                file_data['data'].append([initial_value] * len(time_column))

        # 如果经过处理后仍然没有数据，跳过创建文件
        if not file_data['header']:
            logger.info(f"警告: 文件类型 {file_type} 没有任何数据，跳过创建文件")
            continue

        # 确保所有列都有对应的序号和初始状态数据
        ensure_complete_data(file_data, initial_header, initial_second_row, initial_third_row, station_number_dict,
                             time_column)

        # 创建输出文件，使用序号_处理后_前缀
        output_file = os.path.join(output_dir, NODE_FILES[file_type])

        # 写入CSV文件
        with open(output_file, 'w', newline='', encoding='utf-8') as csvfile:
            # 写入表头（第一行）
            csvfile.write(','.join(['TIME'] + file_data['header']) + '\n')

            # 写入序号（第二行）
            second_row_values = ['""']  # 第一列TIME对应的序号为空
            for col in file_data['header']:
                # 首先尝试从站点-序号映射中获取序号
                sequence_number = ''
                if station_number_dict:
                    # 清理站点名称并查找对应序号
                    cleaned_name = clean_station_name(col)
                    if cleaned_name in station_number_dict:
                        sequence_number = str(station_number_dict[cleaned_name])
                    else:
                        # 如果清理后的名称没找到，尝试使用冒号清理方式
                        cleaned_name_colon = clean_station_name_for_new_conditions(col)
                        if cleaned_name_colon in station_number_dict:
                            sequence_number = str(station_number_dict[cleaned_name_colon])

                # 如果站点-序号映射中没有找到，尝试从初始状态文件中获取
                if not sequence_number and col in initial_header:
                    col_idx = initial_header.index(col)
                    if col_idx < len(initial_second_row):
                        sequence_number = initial_second_row[col_idx]

                # 如果仍然没有找到序号，使用空字符串
                if not sequence_number:
                    sequence_number = '""'

                second_row_values.append(sequence_number)
            csvfile.write(','.join(second_row_values) + '\n')

            # 写入数据（从第三行开始）
            for i in range(len(time_column)):
                row = [f'"{time_column[i]}"']
                for col_idx, col_data in enumerate(file_data['data']):
                    if i < len(col_data):
                        row.append(str(col_data[i]))
                    else:
                        # 如果数据不足，使用初始状态数据
                        col_name = file_data['header'][col_idx]
                        initial_value = get_initial_value_for_column(col_name, initial_header, initial_third_row)

                        # 检查是否为ST特征（压缩机启停）
                        is_st_feature = False
                        if ':ST' in col_name or '_st' in col_name.lower():
                            is_st_feature = True

                        # 对ST特征进行特殊处理
                        if is_st_feature:
                            try:
                                initial_value_float = float(initial_value)
                                processed_value = 1.0 if initial_value_float >= 1000 else 0.0
                                row.append(str(processed_value))
                                logger.info(
                                    f"  - 写入时处理ST特征列 {col_name}: 初始值 {initial_value}, 处理后: {processed_value}")
                            except ValueError:
                                row.append(str(initial_value))
                        else:
                            row.append(str(initial_value))
                csvfile.write(','.join(row) + '\n')

        logger.info(f"已创建文件: {output_file}")


def process_flow_hour_data():
    """
    处理flow_hour数据，匹配仿真节点名

    处理步骤:
    1. 读取上载点和下载点的Excel文件
    2. 从URL获取flow_hour数据
    3. 筛选出北部台的数据
    4. 根据管道、站场阀室和用户名称匹配Excel中的仿真节点名
    5. 确保所有数值非负，负数取绝对值
    6. 为匹配的节点名添加:SNQ或_q后缀，用于与initial_state_file中的名称匹配

    返回:
        dict: 仿真节点名与对应值的字典，包含原始名称和添加后缀的名称
    """
    logger.info(f"处理flow_hour数据...")

    # 读取Excel文件
    try:
        # 读取上游节点小时流量Excel文件

        up_node_hour_flow_file_path = PROJECT_ROOT / settings["filepath"]["up_node_hour_flow_file_path"]
        down_node_hour_flow_file_path = PROJECT_ROOT / settings["filepath"]["down_node_hour_flow_file_path"]
        logger.info(f"上游节点小时流量文件: {up_node_hour_flow_file_path}")
        logger.info(f"下游节点小时流量文件: {down_node_hour_flow_file_path}")
        upload_df = read_excel_compatible(up_node_hour_flow_file_path)
        download_df = read_excel_compatible(down_node_hour_flow_file_path)

        # 合并上传点和下载点数据
        excel_df = pd.concat([upload_df, download_df])

        # 确保Excel有必要的列
        required_columns = ['管道', '站场阀室', '用户名称', '仿真节点名']
        for col in required_columns:
            if col not in excel_df.columns:
                logger.info(f"错误: Excel文件缺少必要的列 '{col}'")
                return {}
    except Exception as e:
        logger.info(f"读取Excel文件出错: {e}")
        return {}

    # 从URL获取flow_hour数据
    flow_hour_url = settings["api_url"]["flow_hour_url"]
    logger.info(f"从URL获取flow_hour数据: {flow_hour_url}")
    flow_data = parse_json_url(flow_hour_url)
    # flow_data 可能直接是列表，或是包含 data 键的字典，做兼容
    if isinstance(flow_data, dict) and 'data' in flow_data:
        flow_data = flow_data['data']
    if not flow_data:
        logger.info(f"无法从URL {flow_hour_url} 获取数据或数据为空")

    logger.info(f"从flow_hour数据源解析了 {len(flow_data)} 条记录")

    # 用于存储匹配的结果
    matched_data = {}
    matched_data_with_suffix = {}  # 存储添加后缀的节点名与对应值
    unmatched_records = []

    # 按北部台分组处理数据
    north_dispatch_data = []
    for record in flow_data:
        try:
            dispatch_name = record.get('dispatchName', '')
            if dispatch_name == '北部台':
                north_dispatch_data.append(record)
        except:
            continue

    logger.info(f"找到 {len(north_dispatch_data)} 条北部台数据")

    # 处理北部台数据
    for record in north_dispatch_data:
        try:
            pipeline_name = record.get('pipelineName', '')
            station_name = record.get('stationName', '')
            gas_name = record.get('gasName')
            customer_name = record.get('customerName')

            # 确定用户名称：优先使用gasName，如果为null则使用customerName
            user_name = gas_name if gas_name is not None else customer_name
            value = float(record.get('value', 0))

            # 允许接受负数值
            # 注释掉原来的绝对值处理
            # if value < 0:
            #     value = abs(value)

            # 在Excel中查找匹配的行
            matching_rows = excel_df[
                (excel_df['管道'] == pipeline_name) &
                (excel_df['站场阀室'] == station_name) &
                (excel_df['用户名称'] == user_name)
                ]

            if not matching_rows.empty:
                # 获取仿真节点名
                sim_node = matching_rows.iloc[0]['仿真节点名']

                # 保存原始匹配的数据
                matched_data[sim_node] = value

                # 添加:SNQ后缀的版本
                sim_node_snq = f"{sim_node}:SNQ"
                matched_data_with_suffix[sim_node_snq] = value

                # 添加_q后缀的版本
                sim_node_q = f"{sim_node}_q"
                matched_data_with_suffix[sim_node_q] = value
            else:
                unmatched_records.append({
                    'pipeline_name': pipeline_name,
                    'station_name': station_name,
                    'user_name': user_name,
                    'value': value
                })
        except Exception as e:
            logger.info(f"处理记录时出错: {e}")
            continue

    # 打印未匹配的记录数量
    if unmatched_records:
        logger.info(f"找到 {len(unmatched_records)} 条无法匹配的记录")

    logger.info(f"成功匹配 {len(matched_data)} 条flow_hour记录")
    logger.info(f"生成了 {len(matched_data_with_suffix)} 条带后缀的flow_hour记录")

    # 合并原始数据和带后缀的数据
    result_data = {**matched_data, **matched_data_with_suffix}
    return result_data


# 全局变量，用于缓存sensor数据和flow_data，避免重复获取
_cached_sensor_data = None
global_flow_data = {}


def process_sensor_data(force_refresh=False, time_column=None):
    """
    增强版sensor_url数据处理函数，返回所有opcAddress和对应的currValue

    处理步骤：
    1. 从URL获取sensor数据（如果缓存不存在或强制刷新）
    2. 解析每条记录的opcAddress和currValue
    3. 将时间戳转换为北京时间
    4. 根据时间戳对数据进行分组，确保每个5分钟时间间隔内使用最接近的数据
    5. 特殊处理ST和FR控制特征的值：
       - ST: 如果值>=1000，则取1.0，否则取0.0
       - FR: 如果值为1.0或4.0，则取1.0，否则取0.0
    6. 检查opcAddress是否在real_data_reflection.xlsx的第6-107列中存在
    7. 精确匹配opcAddress字符串，确保数据准确性

    Args:
        force_refresh (bool): 是否强制从URL重新获取数据，默认为False

    返回:
        dict: opcAddress与对应值的字典，包含所有数据用于后续筛选
    """
    global _cached_sensor_data

    # 如果缓存存在且不需要强制刷新，直接返回缓存数据
    if _cached_sensor_data is not None and not force_refresh:
        logger.info("使用缓存的sensor数据，避免重复获取")
        return _cached_sensor_data

    logger.info("\n处理sensor_url数据（增强版）...")

    # 从URL获取sensor数据
    sensor_url = settings["api_url"]["sensor_url"]
    logger.info(f"从URL获取sensor数据: {sensor_url}")
    sensor_data = parse_json_url(sensor_url)
    if not sensor_data:
        logger.info(f"无法从URL {sensor_url} 获取数据或数据为空")
        return {}

    logger.info(f"从sensor数据源解析了 {len(sensor_data)} 条记录")

    # 用于存储所有数据（每个opcAddress对应按5分钟对齐的时间序列）
    all_sensor_data = {}
    negative_records = []

    # 构建5分钟时间间隔（优先使用传入的时间列）
    time_intervals = []
    if time_column and isinstance(time_column, (list, tuple)) and len(time_column) > 0:
        for ts in time_column:
            try:
                time_intervals.append(datetime.strptime(ts, "%Y/%m/%d %H:%M:%S"))
            except Exception:
                continue
    else:
        now = datetime.now()
        end_of_day = datetime(now.year, now.month, now.day, 23, 59, 59)
        current_time = datetime(now.year, now.month, now.day, 0, 0, 0)
        while current_time <= end_of_day:
            time_intervals.append(current_time)
            current_time += timedelta(minutes=5)

    logger.info(f"生成了 {len(time_intervals)} 个5分钟时间间隔用于对齐sensor数据")

    # 按opcAddress分组存储数据
    grouped_data = {}

    # 处理每条传感器记录
    for record in sensor_data:
        try:
            opc_address = record.get('opcAddress', '')
            if not opc_address:
                continue

            # 确保opcAddress是字符串类型，便于精确匹配
            opc_address = str(opc_address).strip()
            curr_value = float(record.get('currValue', 0))
            update_time_str = record.get('updateTime', '')

            # 如果currValue为-1.0，则舍去这个数值，当做缺失列处理
            if curr_value == -1.0:
                # logger.info(f"舍弃currValue为-1.0的记录: opcAddress={opc_address}")
                continue

            # 允许接受其他负数值
            # 注释掉原来的绝对值处理
            # if curr_value < 0:
            #     negative_records.append({
            #         'opc_address': opc_address,
            #         'original_value': curr_value,
            #         'new_value': abs(curr_value)
            #     })
            #     curr_value = abs(curr_value)

            # 将时间戳转换为datetime对象（毫秒级时间戳）
            if update_time_str:
                try:
                    # 转换毫秒时间戳为datetime对象（北京时间）
                    update_time = datetime.fromtimestamp(int(update_time_str) / 1000)

                    # 将数据添加到分组中
                    if opc_address not in grouped_data:
                        grouped_data[opc_address] = []

                    # 记录原始的opcAddress值，便于后续匹配
                    grouped_data[opc_address].append({
                        'time': update_time,
                        'value': curr_value
                    })
                except ValueError as e:
                    logger.info(f"时间戳转换错误: {e}, 时间戳: {update_time_str}")

        except Exception as e:
            logger.info(f"处理传感器记录时出错: {e}")
            continue

    # 对于每个opcAddress，按每个5分钟间隔选择最近的值，构建时间序列
    for opc_address, time_values in grouped_data.items():
        time_values.sort(key=lambda x: x['time'])
        aligned_series = []
        for interval in time_intervals:
            closest_data = None
            min_time_diff = timedelta.max
            for data in time_values:
                # 优先使用小于等于目标时间的点；未来点有轻微惩罚
                diff = data['time'] - interval
                time_diff = abs(diff) + (timedelta(seconds=1) if diff.total_seconds() > 0 else timedelta(0))
                if time_diff < min_time_diff:
                    min_time_diff = time_diff
                    closest_data = data
            aligned_series.append(closest_data['value'] if closest_data else 0.0)
        all_sensor_data[opc_address] = aligned_series

    if negative_records:
        logger.info(f"转换了 {len(negative_records)} 条负数值为绝对值")

    # ST/FR等控制逻辑在结合特征映射时处理（此处保留原始序列）

    logger.info(f"成功处理 {len(all_sensor_data)} 个传感器数据(时间序列)")

    # 更新缓存
    _cached_sensor_data = all_sensor_data

    # 记录所有opcAddress的字符串表示，便于调试
    logger.info(f"sensor_data中共有 {len(all_sensor_data)} 个opcAddress")

    return all_sensor_data

# def process_real_data_reflection(reflection_df, sensor_data, no_data_headers):
#     """
#     处理real_data_reflection.xlsx文件中的数据，根据映射关系选择最优的opcAddress数据
#
#     处理步骤：
#     1. 遍历real_data_reflection.xlsx文件中的每一行
#     2. 获取节点名称、压力对比type值和组分对比type值
#     3. 根据type值确定特征名称（如p-、p+或组分特征）
#     4. 在第6-107列中查找对应的opcAddress
#     5. 选择非0、非负、没有突变的有效数据
#     6. 构建节点特征组合名称，与NO_data文件的列名进行匹配
#     7. 确保opcAddress的匹配是精确的，包括完整的opcAddress字符串
#     8. 记录详细的匹配日志，便于调试和追踪
#
#     Args:
#         reflection_df: real_data_reflection.xlsx文件的DataFrame
#         sensor_data: 所有sensor数据字典
#         no_data_headers: NO_data文件的列名列表，用于匹配节点特征组合名称
#
#     Returns:
#         dict: 节点特征组合名称与对应最优值的字典
#     """
#     logger.info(f"处理real_data_reflection.xlsx文件中的数据...")
#
#     reflection_data = {}
#
#     # 记录sensor_data中的opcAddress数量
#     logger.info(f"sensor_data中共有 {len(sensor_data)} 个opcAddress")
#
#     # 组分特征列表
#     component_features = ['NC1', 'NC2', 'NC3', 'NCO2', 'NN2', 'NNC4', 'NIC4', 'NH2S', 'NIC5', 'NNC5', 'NNC6']
#
#     # 遍历real_data_reflection.xlsx文件中的每一行
#     for _, row in reflection_df.iterrows():
#         try:
#             # 获取节点名称
#             node_name = row.get('节点名称', '')
#             if not node_name:
#                 continue
#
#             # 获取压力对比type值和组分对比type值
#             pressure_type = row.get('压力对比type值', None)
#             component_type = row.get('组分对比type值', None)
#
#             # 初始化特征名称列表
#             feature_names = []
#
#             # 根据压力对比type值确定特征名称
#             if pd.notna(pressure_type):
#                 if pressure_type == 1:
#                     # 对应特征p-
#                     feature_names.append('p-')
#                 elif pressure_type == 2:
#                     # 对应特征p+
#                     feature_names.append('p+')
#
#             # 根据组分对比type值确定特征名称
#             if pd.notna(component_type) and component_type == 16:
#                 # 对应组分特征列表
#                 feature_names.extend(component_features)
#
#             # 为每个特征选择最优的opcAddress数据
#             for feature_name in feature_names:
#                 # 构建节点特征组合名称
#                 combined_name = f"{node_name}_{feature_name}"
#
#                 # 检查是否与NO_data文件的列名匹配
#                 if no_data_headers and combined_name not in no_data_headers:
#                     # 尝试检查NO_data_initial_state.csv中是否有匹配的列名
#                     no_data_initial_state_path = os.path.join(INITIAL_STATE_DIR, "NO_data_initial_state.csv")
#                     if os.path.exists(no_data_initial_state_path):
#                         try:
#                             # 读取NO_data_initial_state.csv文件的第一行（列名）
#                             with open(no_data_initial_state_path, 'r', encoding='utf-8') as f:
#                                 header_line = f.readline().strip()
#                                 headers = [col.strip() for col in header_line.split(',')]
#
#                             if combined_name in headers:
#                                 # 继续处理，不跳过
#                                 pass
#                             else:
#                                 pass
#                         except Exception as e:
#                             logger.info(f"读取NO_data_initial_state.csv文件时出错: {str(e)}")
#                             continue
#                     else:
#                         continue
#
#                 # 收集该行所有可用的opcAddress数据
#                 available_data = []
#                 for col_idx in range(6, 108):
#                     col_name = f'转置数据_{col_idx}'
#                     if col_name in reflection_df.columns:
#                         opc_address = row[col_name]
#                         if pd.notna(opc_address) and str(opc_address).strip():
#                             # 精确匹配opcAddress
#                             opc_address_str = str(opc_address).strip()
#
#                             # 记录当前处理的opcAddress
#
#                             # 检查是否在sensor_data中存在
#                             if opc_address_str in sensor_data:
#                                 curr_value = sensor_data[opc_address_str]
#
#                                 # 检查数据质量：非0、非负、没有突变
#                                 if curr_value > 0 and is_valid_sensor_value(curr_value):
#                                     available_data.append((opc_address_str, curr_value, col_idx))
#
#                                 else:
#                                     pass
#                             else:
#                                 pass
#
#                 # 选择最优数据
#                 if available_data:
#                     # 选择第一个有效数据（可以根据需要调整选择策略）
#                     selected_opc, selected_value, col_idx = available_data[0]
#                     reflection_data[combined_name] = selected_value
#
#         except Exception as e:
#             logger.info(f"处理real_data_reflection.xlsx行时出错: {str(e)}")
#
#     logger.info(f"成功从real_data_reflection.xlsx中为 {len(reflection_data)} 个节点特征选择了最优数据")
#
#     # 打印所有选择的节点特征和对应的值，便于调试
#     if reflection_data:
#         pass
#     return reflection_data


def select_optimal_sensor_data_from_input_feature(input_node_feature_df, sensor_data, no_data_headers=None):
    """
    根据input_node_feature.xlsx文件为每个节点选择最优的opcAddress数据

    处理步骤：
    1. 遍历input_node_feature.xlsx文件中的每一行
    2. 获取节点名称、特征名称和opcAddress
    3. 根据控制特征英文名(SP, FR, SP+, SPD, ST)进行特殊处理
    4. 对ST和FR控制方式进行特殊处理：
       - ST: 如果值>=1000，则取1.0，否则取0.0
       - FR: 如果值为1.0或4.0，则取1.0，否则取0.0
    5. 从real_data_reflection.xlsx文件中查找匹配的opcAddress数据

    Args:
        input_node_feature_df: input_node_feature.xlsx文件的DataFrame
        sensor_data: 所有sensor数据字典
        no_data_headers: NO_data文件的列名列表，用于匹配节点特征组合名称

    Returns:
        dict: 节点特征组合名称与对应最优值的字典
    """
    logger.info(f"根据input_node_feature.xlsx文件为每个节点选择最优的opcAddress数据...")

    node_feature_data = {}

    def elementwise_sum(series_list, length):
        result = [0.0] * length
        for s in series_list:
            if isinstance(s, (list, tuple, np.ndarray)):
                for i in range(min(length, len(s))):
                    result[i] += float(s[i]) if s[i] is not None else 0.0
            else:
                for i in range(length):
                    result[i] += float(s)
        return result

    def apply_control_rules(series, control_feature):
        if not isinstance(series, (list, tuple, np.ndarray)):
            series = [series]
        processed = []
        if control_feature == 'ST':
            for v in series:
                if v == 1.0 or v == 0.0:
                    processed.append(v)
                else:
                    processed.append(1.0 if float(v) >= 1000 else 0.0)
        elif control_feature == 'FR':
            for v in series:
                processed.append(1.0 if float(v) in (1.0, 4.0) else 0.0)
        else:
            processed = [float(v) for v in series]
        return processed

    def series_length_hint():
        # 取任意一个opc的序列长度作为参考
        for v in sensor_data.values():
            if isinstance(v, (list, tuple, np.ndarray)):
                return len(v)
        return 0

    series_len = series_length_hint()

    # 读取real_data_reflection.xlsx文件
    real_data_reflection_file = PROJECT_ROOT / settings["filepath"]["real_data_reflection_file_path"]
    if os.path.exists(real_data_reflection_file):
        logger.info(f"读取real_data_reflection.xlsx文件: {real_data_reflection_file}")
        reflection_df = read_excel_compatible(real_data_reflection_file)

        # 处理real_data_reflection.xlsx文件中的数据
        reflection_data = process_real_data_reflection(reflection_df, sensor_data, no_data_headers)
        if reflection_data:
            logger.info(f"从real_data_reflection.xlsx文件中找到 {len(reflection_data)} 个匹配的节点特征数据")
            # 将reflection_data合并到node_feature_data中
            node_feature_data.update(reflection_data)

    # 从down_node_hour_flow.xlsx补充映射: opcAddress -> 站点英文名，产生 :SNQ 和 _q 两种命名
    try:
        down_node_hour_flow_file_path = PROJECT_ROOT / settings["filepath"]["down_node_hour_flow_file_path"]
        if os.path.exists(down_node_hour_flow_file_path):
            df_down = read_excel_compatible(down_node_hour_flow_file_path)
            # 容错取列
            # 优先用列名，否则按位置
            col_opc = 'opcAddress' if 'opcAddress' in df_down.columns else df_down.columns[0]
            col_site = '站点英文名' if '站点英文名' in df_down.columns else df_down.columns[2]
            for _, r in df_down.iterrows():
                try:
                    opc = str(r.get(col_opc, '')).strip()
                    site = str(r.get(col_site, '')).strip()
                    if not opc or not site:
                        continue
                    if opc in sensor_data:
                        series = sensor_data[opc]
                        name_snq = f"{site}:SNQ"
                        name_q = f"{site}_q"
                        node_feature_data[name_snq] = series
                        node_feature_data[name_q] = series
                except Exception:
                    continue
    except Exception as e:
        logger.info(f"读取down_node_hour_flow.xlsx失败或处理出错: {e}")

    # 遍历input_node_feature.xlsx文件中的每一行
    for _, row in input_node_feature_df.iterrows():
        try:
            # 获取节点名称和特征名称
            node_name = row.get('站点英文名', '')  # 第3列：站点英文名
            control_feature = row.get('控制特征英文名', '')  # 第5列：控制特征英文名
            opc_address = row.get('opcAddress', '')  # 第8列：opcAddress

            if not node_name or not control_feature or not opc_address:
                continue

            # 构建组合名称 - 同时考虑两种格式（冒号和下划线）
            combined_name_colon = f"{node_name}:{control_feature}"

            # 根据控制特征英文名映射到对应的后缀
            feature_suffix = ''
            if control_feature == 'SP':
                feature_suffix = 'p'
            elif control_feature == 'FR':
                feature_suffix = 'fr'
            elif control_feature == 'SP+':
                feature_suffix = 'p+'
            elif control_feature == 'SPD':
                feature_suffix = 'pd'
            elif control_feature == 'ST':
                feature_suffix = 'st'

            # 使用下划线格式构建第二种组合名称
            combined_name_underscore = f"{node_name}_{feature_suffix}" if feature_suffix else ""

            # 默认使用冒号格式
            combined_name = combined_name_colon

            # 检查opcAddress是否在sensor数据中
            if opc_address in sensor_data:
                base_series = sensor_data[opc_address]
                # SNQ需要对8-11列做求和
                if control_feature == 'SNQ' and series_len > 0:
                    series_list = []
                    try:
                        # 列按位置：第8-11列（1-based）=> iloc 7..10
                        for pos in range(7, 11):
                            try:
                                other_opc = row.iloc[pos]
                            except Exception:
                                other_opc = None
                            if pd.notna(other_opc):
                                other_opc = str(other_opc).strip()
                                if other_opc in sensor_data:
                                    series_list.append(sensor_data[other_opc])
                    except Exception:
                        pass
                    if not series_list:
                        series_list = [base_series]
                    summed = elementwise_sum(series_list, series_len)
                    processed_series = summed
                else:
                    processed_series = base_series if isinstance(base_series, (list, tuple, np.ndarray)) else [
                        base_series]

                # 根据控制特征英文名进行特殊处理(ST/FR)
                processed_series = apply_control_rules(processed_series, control_feature)

                # 质量检查：至少有一个正值
                if any(is_valid_sensor_value(val) for val in processed_series):
                    node_feature_data[combined_name_colon] = processed_series
                    if combined_name_underscore:
                        node_feature_data[combined_name_underscore] = processed_series
        except Exception as e:
            logger.info(f"处理节点特征时出错: {e}")
            continue

    logger.info(f"成功为 {len(node_feature_data)} 个节点特征选择了最优数据/时间序列")
    return node_feature_data


def process_real_data_reflection(reflection_df, sensor_data, no_data_headers):
    """
    处理real_data_reflection.xlsx文件中的数据

    处理步骤：
    1. 遍历reflection_df中的每一行
    2. 获取节点名称、压力对比type值和组分对比type值
    3. 根据type值确定特征名称
    4. 在第6-107列中查找opcAddress
    5. 选择非0非负没有突变的数据作为该节点输入给代理模型的数据
    6. 确保opcAddress的匹配是精确的，包括完整的opcAddress字符串

    Args:
        reflection_df: real_data_reflection.xlsx文件的DataFrame
        sensor_data: 所有sensor数据字典
        no_data_headers: NO_data文件的列名列表，用于匹配节点特征组合名称

    Returns:
        dict: 节点特征组合名称与对应最优值的字典
    """
    logger.info(f"处理real_data_reflection.xlsx文件中的数据...")

    node_feature_data = {}

    # 如果no_data_headers为None，则无法进行匹配
    if no_data_headers is None:
        logger.info("NO_data文件的列名列表为空，无法进行匹配")
        return node_feature_data

    # 辅助函数：从序列中获取代表性标量（用于有效性判断）
    def get_scalar_from_series(value):
        try:
            if isinstance(value, (list, tuple, np.ndarray)):
                for v in reversed(list(value)):
                    if v is None:
                        continue
                    fv = float(v)
                    if not np.isnan(fv):
                        return fv
                return float('nan')
            else:
                return float(value)
        except Exception:
            return float('nan')

    # 遍历reflection_df中的每一行
    for _, row in reflection_df.iterrows():
        try:
            # 获取节点名称、压力对比type值和组分对比type值
            node_name = row['nodename']
            pressure_type = row['压力对比type值']
            component_type = row['组分对比type值']

            if not node_name:
                continue

            # 确定特征名称列表
            feature_names = []
            if pd.notna(pressure_type):
                if pressure_type in (1,2):
                    feature_names.append('p')

            if pd.notna(component_type) and component_type == 16:
                # 组分特征
                component_features = ['NC1', 'NC2', 'NC3', 'NCO2', 'NN2', 'NNC4', 'NIC4', 'NH2S', 'NIC5', 'NNC5',
                                      'NNC6']
                logger.info(f"节点 {node_name} 的组分对比type值为16，对应组分特征列表: {', '.join(component_features)}")
                feature_names.extend(component_features)

            # 为每个特征查找opcAddress
            for feature_name in feature_names:
                # 构建两种格式的组合名称
                combined_name_underscore = f"{node_name}_{feature_name}"
                combined_name_colon = f"{node_name}:{feature_name}"

                # 记录节点特征组合名称
                # logger.info(f"构建节点特征组合名称: 下划线格式={combined_name_underscore}，冒号格式={combined_name_colon}")
                # logger.info(f"节点名称: {node_name}，特征名称: {feature_name}")

                # 检查两种格式的组合名称是否在NO_data文件的列名中
                underscore_in_headers = combined_name_underscore in no_data_headers
                colon_in_headers = combined_name_colon in no_data_headers

                if not underscore_in_headers and not colon_in_headers:
                    logger.info(
                        f"组合名称 {combined_name_underscore} 和 {combined_name_colon} 都不在NO_data文件的列名中，跳过")

                    # 尝试检查NO_data_initial_state.csv中是否有匹配的列名
                    no_data_initial_state_path = os.path.join(INITIAL_STATE_DIR, "NO_data_initial_state.csv")
                    if os.path.exists(no_data_initial_state_path):
                        try:
                            # 读取NO_data_initial_state.csv文件的第一行（列名）
                            lines = read_csv_with_open_compatible(no_data_initial_state_path)
                            if lines:
                                header_line = lines[0].strip()
                                headers = [col.strip() for col in header_line.split(',')]
                                #logger.info(f"NO列名前五个{headers[:5]}")

                            # 检查两种格式是否在NO_data_initial_state.csv中
                            underscore_in_initial = combined_name_underscore in headers
                            colon_in_initial = combined_name_colon in headers

                            if underscore_in_initial or colon_in_initial:
                                logger.info(
                                    f"节点特征组合名称在NO_data_initial_state.csv中找到: 下划线格式={underscore_in_initial}, 冒号格式={colon_in_initial}")
                                # 更新匹配状态
                                if underscore_in_initial:
                                    underscore_in_headers = True
                                if colon_in_initial:
                                    colon_in_headers = True
                            else:
                                pass
                                logger.info(f"节点特征组合名称在NO_data_initial_state.csv中也未找到，跳过")
                                continue
                        except Exception as e:
                            logger.info(f"读取NO_data_initial_state.csv文件时出错: {str(e)}")
                            continue
                    else:
                        continue

                # 选择在headers中存在的格式作为主要格式
                combined_name = combined_name_colon if colon_in_headers else combined_name_underscore

                # 收集该行所有可用的opcAddress数据
                available_data = []
                for col_idx in range(6, 108):
                    col_name = f'转置数据_{col_idx}'
                    if col_name in reflection_df.columns:
                        opc_address = row[col_name]
                        if pd.notna(opc_address) and str(opc_address).strip():
                            # 精确匹配opcAddress
                            opc_address_str = str(opc_address).strip()

                            # 记录当前处理的opcAddress

                            # 检查是否在sensor_data中存在
                            if opc_address_str in sensor_data:
                                curr_series = sensor_data[opc_address_str]
                                curr_scalar = get_scalar_from_series(curr_series)
                                # 检查数据质量：非0、非负、没有突变（简单检查）
                                if np.isfinite(curr_scalar) and curr_scalar > 0 and is_valid_sensor_value(curr_scalar):
                                    available_data.append((opc_address_str, curr_series, col_idx))

                                else:
                                    pass
                            else:
                                pass

                # 选择最优数据
                if available_data:
                    # 选择第一个有效数据（可以根据需要调整选择策略）
                    selected_opc, selected_series, col_idx = available_data[0]

                    # 同时添加两种格式的组合名称到结果字典中（存储序列）
                    if colon_in_headers:
                        node_feature_data[combined_name_colon] = selected_series
                        logger.info(f"为 {combined_name_colon} 选择了 opcAddress: {selected_opc} (列索引: {col_idx})")
                        # 记录该节点特征与opcAddress的对应关系，便于后续评估
                        logger.info(f"节点特征 {combined_name_colon} 对应的opcAddress: {selected_opc}")

                    if underscore_in_headers:
                        node_feature_data[combined_name_underscore] = selected_series
                        logger.info(
                            f"为 {combined_name_underscore} 选择了 opcAddress: {selected_opc} (列索引: {col_idx})")
                        # 记录该节点特征与opcAddress的对应关系，便于后续评估
                        logger.info(f"节点特征 {combined_name_underscore} 对应的opcAddress: {selected_opc}")
        except Exception as e:
            logger.info(f"处理real_data_reflection行时出错: {e}")
            continue

    # 打印所有选择的节点特征和对应的值，便于调试
    if node_feature_data:
        logger.info(f"成功从real_data_reflection.xlsx中为 {len(node_feature_data)} 个节点特征选择了最优数据")
        logger.info("选择的节点特征和值:")
        for name, value in list(node_feature_data.items())[:10]:  # 只打印前10个作为示例
            logger.info(f"  - {name}: {value}")
    else:
        logger.info("未从real_data_reflection.xlsx中找到任何匹配的节点特征数据")

    return node_feature_data


def is_valid_sensor_value(value):
    """
    检查传感器值是否有效（没有突变等异常）

    Args:
        value: 传感器值

    Returns:
        bool: 是否有效
    """
    # 简单的有效性检查，可以根据需要扩展
    if value <= 0:
        return False

    # 检查是否为异常大的值（可能是突变）
    if value > 1e6:
        return False

    return True


def parse_json_url(url):
    """
    从URL获取并解析可能格式不正确的JSON数据

    参数:
        url (str): JSON数据的URL

    返回:
        list: 解析后的JSON对象列表
    """
    try:
        # 从URL获取数据
        response = requests.get(url, timeout=30)
        response.raise_for_status()  # 如果请求失败，抛出异常
        content = response.text.strip()

        # 首先尝试直接解析
        try:
            return json.loads(content)
        except json.JSONDecodeError as e:
            # 尝试修复常见的JSON格式问题
            # 1. 移除末尾多余的逗号
            content = re.sub(r',\s*}', '}', content)
            content = re.sub(r',\s*]', ']', content)

            # 2. 处理不完整的数组和对象
            if content.count('[') > content.count(']'):
                content += ']' * (content.count('[') - content.count(']'))
            if content.count('{') > content.count('}'):
                content += '}' * (content.count('{') - content.count('}'))

            # 3. 尝试截取到最后一个完整的JSON对象
            try:
                # 找到最后一个完整的对象或数组
                lines = content.split('\n')
                for i in range(len(lines) - 1, -1, -1):
                    try:
                        partial_content = '\n'.join(lines[:i + 1])
                        # 确保JSON结构完整
                        if partial_content.strip().endswith(('}', ']')):
                            return json.loads(partial_content)
                    except json.JSONDecodeError:
                        continue

                # 如果还是失败，尝试使用正则表达式提取JSON对象
                json_objects = re.findall(r'{[^{}]*}', content)
                if json_objects:
                    return [json.loads(obj) for obj in json_objects]
            except Exception:
                pass

            # 最后的备选方案：返回空列表
            logger.info(f"无法解析URL {url} 的JSON数据，返回空列表")
            return []

    except Exception as e:
        logger.info(f"从URL获取数据失败: {e}")
        return []


def remove_second_column_and_duplicate_time_columns(input_dir):
    """
    删除所有节点类型CSV文件的第2列，以及所有重复的时间列，确保只保留第一列的TIME列
    包括表头行和序号行，并清除第一列第2行单元格中的内容

    Args:
        input_dir: 输入文件目录
    """
    logger.info("正在处理CSV文件，删除第2列、重复时间列并清除第一列第2行单元格内容...")

    for file_type, file_name in NODE_FILES.items():
        file_path = os.path.join(input_dir, file_name)
        if not os.path.exists(file_path):
            logger.info(f"警告: 文件 {file_path} 不存在，跳过")
            continue

        try:
            # 读取CSV文件（使用兼容性增强版本）
            lines = read_csv_with_open_compatible(file_path)

            # 首先分析表头行，确定要删除的列索引
            if len(lines) > 0:
                header_columns = lines[0].strip().split(',')
                # 删除第2列（索引为1）
                header_columns = header_columns[:1] + header_columns[2:]

                # 找出所有重复的时间列索引（除了第一列TIME）
                time_column_indices = []
                for j, col in enumerate(header_columns[1:], 1):
                    if col.strip().lower() in ['时间', 'time']:
                        time_column_indices.append(j)

                # 从后往前删除，避免索引变化
                time_column_indices.reverse()

                # 处理每一行，删除第2列和重复的时间列
                processed_lines = []
                for i, line in enumerate(lines):
                    columns = line.strip().split(',')
                    if len(columns) > 1:  # 确保行有足够的列
                        # 删除第2列（索引为1）
                        columns = columns[:1] + columns[2:]

                        # 删除所有重复的时间列
                        for idx in time_column_indices:
                            if idx < len(columns):
                                columns = columns[:idx] + columns[idx + 1:]

                        # 如果是第2行（序号行），清除第一列的内容
                        if i == 1 and len(columns) > 0:
                            columns[0] = '""'  # 使用空字符串替换第一列内容

                        new_line = ','.join(columns)
                        processed_lines.append(new_line + '\n')
                    else:
                        processed_lines.append(line)  # 保持原样

                # 写回文件
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.writelines(processed_lines)

                logger.info(
                    f"已处理文件: {file_path}，删除第2列、{len(time_column_indices)}个重复时间列并清除第一列第2行单元格内容")
            else:
                logger.info(f"警告: 文件 {file_path} 为空，跳过")

        except Exception as e:
            logger.info(f"处理文件 {file_path} 时出错: {str(e)}")

def simulate_pipeline_network(input_data=None, case_dir=None, yc_data=None):
    """
    使用代理模型进行天然气管网预测，支持最大步数限制

    如果未提供input_data，则自动调用数据转换模块获取数据
    如果未提供case_dir，则自动创建新的保存目录
    """
    try:
        # 如果未提供输入数据，则自动调用数据转换
        if input_data is None:
            logger.info("未提供输入数据，将自动调用数据转换模块获取数据")
            input_data = convert_data_and_get_sample()
        logger.info(f"已获取输入数据:{input_data}")

        # 获取步数限制参数
        max_steps = input_data.pop("_max_steps", 144)  # 默认13步-->时间步修改为144

        # 创建保存目录（如果未提供）
        if case_dir is None:
            case_dir = create_save_directory()
            logger.info(f"预测结果将保存到: {case_dir}")

        # 处理输入数据并生成代理模型所需的CSV文件
        process_input_data_for_proxy_model(input_data, INITIAL_STATE_DIR, INPUT_FILES_DIR)

        # 如有上传CSV，覆盖 YC 数据文件内容
        try:
            if isinstance(yc_data, str) and len(yc_data) > 0:
                yc_file_path = os.path.join(INPUT_FILES_DIR, NODE_FILES['yc'])
                with open(yc_file_path, 'w', encoding='utf-8', newline='') as f:
                    f.write(yc_data)
                logger.info(f"已覆盖YC文件: {yc_file_path}")
            else:
                logger.info(f"yc覆盖失败")
        except Exception as e:
            logger.info(f"覆盖YC文件失败（忽略继续）: {e}")

        # 删除所有CSV文件的第2列和重复时间列
        remove_second_column_and_duplicate_time_columns(INPUT_FILES_DIR)

        # 检查模型文件是否存在
        if not os.path.exists(MODEL_PATH):
            raise FileNotFoundError(f"错误: 模型文件 {MODEL_PATH} 不存在")

        # 初始化预测器
        predictor = ModelPredictor(MODEL_PATH)

        # 检查必要的文件是否存在
        missing_files = []
        for key, file_name in NODE_FILES.items():
            if not os.path.exists(os.path.join(INPUT_FILES_DIR, file_name)):
                missing_files.append(file_name)

        if missing_files:
            raise FileNotFoundError(f"错误: 以下文件在输入目录中不存在: {', '.join(missing_files)}")

        logger.info(f"从目录加载数据: {INPUT_FILES_DIR}")
        input_data_model, control_nodes = load_input_data_from_files(
            data_dir=INPUT_FILES_DIR,
            node_files=NODE_FILES,
            processor_info=predictor.model_info['processor_info'],
            window_size=predictor.window_size
        )

        # 将工况节点信息添加到processor中
        predictor.processor.control_nodes = control_nodes
        # 同时更新normalizer中的processor
        predictor.normalizer.processor = predictor.processor

        # 获取窗口大小
        window_size = predictor.window_size

        # 确保输入数据足够大（允许等于窗口大小，仅用于作为上下文）
        if input_data_model.shape[0] < window_size:
            raise ValueError(f"错误: 输入数据时间步数 {input_data_model.shape[0]} 小于窗口大小 {window_size}")

        # 预测步数：不依赖输入长度，直接使用最大步数配置
        num_steps = max_steps
        logger.info(f"开始预测，从第{window_size + 1}个时间步开始，预测{num_steps}步...")

        # 转换为张量
        input_tensor = torch.tensor(input_data_model, dtype=torch.float32)

        # 标准化输入数据
        input_norm = predictor.normalize_input(input_data_model)

        # 处理标准化后的数据
        if isinstance(input_norm, torch.Tensor):
            input_norm_tensor = input_norm.clone().detach().to(dtype=torch.float32).to(predictor.device)
        else:
            input_norm_tensor = torch.tensor(input_norm, dtype=torch.float32).to(predictor.device)

        # 进行预测
        all_predictions = []

        # 使用最后一个时间步作为起始输入
        current_input = input_norm_tensor[window_size - 1:window_size].clone()

        # 准备历史窗口数据
        window_data = []
        for w in range(window_size):
            idx = window_size - window_size + w
            window_data.append(input_norm_tensor[idx:idx + 1].clone())

        # 一次性预测多个时间步
        with torch.no_grad():
            multi_step_outputs = predictor.model(
                current_input,
                predictor.edge_index,
                is_training=False,
                window_data=window_data,
                num_steps=num_steps
            )

            # 处理多步预测结果
            for step in range(num_steps):
                # 获取当前步的预测结果
                output = multi_step_outputs[step:step + 1]

                # 保留工况特征的原始值
                for node_idx in range(output.shape[1]):
                    original_node_id = predictor.node_mapping[node_idx]
                    node_type = predictor.node_types.get(original_node_id, "Unknown")

                    # 检查是否是工况节点
                    is_control_node = original_node_id in predictor.processor.control_nodes

                    # 如果是E类型节点且是工况点，保留其p、q或气体组分特征的原始值
                    if node_type == 'E' and is_control_node:
                        control_feature = predictor.processor.control_nodes[original_node_id]['feature']
                        if control_feature in ['p', 'q'] or control_feature.startswith('N'):
                            feat_idx = predictor.node_features.index(control_feature)
                            # 只在有实际数据时才保留原始值
                            if step + window_size < input_norm_tensor.shape[0]:
                                output[0, node_idx, feat_idx] = input_norm_tensor[
                                    step + window_size, node_idx, feat_idx]

                    # 如果是B类型节点，保留其fr特征的原始值
                    elif node_type == 'B':
                        fr_idx = predictor.node_features.index('fr')
                        if step + window_size < input_norm_tensor.shape[0]:
                            output[0, node_idx, fr_idx] = input_norm_tensor[step + window_size, node_idx, fr_idx]

                    # 如果是KC类型节点，保留其st特征和p+特征的原始值
                    elif node_type == 'KC':
                        # 保留st特征
                        st_idx = predictor.node_features.index('st')
                        if step + window_size < input_norm_tensor.shape[0]:
                            output[0, node_idx, st_idx] = input_norm_tensor[step + window_size, node_idx, st_idx]
                        # 检查是否是p+工况点
                        if is_control_node and predictor.processor.control_nodes[original_node_id]['feature'] == 'p+':
                            p_plus_idx = predictor.node_features.index('p+')
                            if step + window_size < input_norm_tensor.shape[0]:
                                output[0, node_idx, p_plus_idx] = input_norm_tensor[
                                    step + window_size, node_idx, p_plus_idx]

                    # 如果是RE类型节点，保留其p+特征的原始值
                    elif node_type == 'RE' and is_control_node:
                        if predictor.processor.control_nodes[original_node_id]['feature'] == 'p+':
                            p_plus_idx = predictor.node_features.index('p+')
                            if step + window_size < input_norm_tensor.shape[0]:
                                output[0, node_idx, p_plus_idx] = input_norm_tensor[
                                    step + window_size, node_idx, p_plus_idx]

                    # 如果是RG类型节点，保留其pd特征的原始值
                    elif node_type == 'RG' and is_control_node:
                        if predictor.processor.control_nodes[original_node_id]['feature'] == 'pd':
                            pd_idx = predictor.node_features.index('pd')
                            if step + window_size < input_norm_tensor.shape[0]:
                                output[0, node_idx, pd_idx] = input_norm_tensor[step + window_size, node_idx, pd_idx]

                # 保存预测结果
                all_predictions.append(output.cpu().numpy())

        # 合并所有预测结果
        if all_predictions:
            predictions = np.concatenate(all_predictions, axis=0)

            # 反标准化预测结果
            logger.info("反标准化预测结果...")
            predictions_tensor = torch.tensor(predictions)
            if len(predictions_tensor.shape) != 3:
                logger.info(f"调整预测结果形状 {predictions_tensor.shape} 以适应反标准化函数")
                if len(predictions_tensor.shape) == 2:
                    predictions_tensor = predictions_tensor.unsqueeze(1)
                elif len(predictions_tensor.shape) == 4:
                    num_steps, batch_size, num_nodes, num_features = predictions_tensor.shape
                    predictions_tensor = predictions_tensor.reshape(num_steps * batch_size, num_nodes, num_features)

            predictions_denorm = predictor.normalizer.inverse_transform_nodes(predictions_tensor)

            # 保存预测结果
            output_data = predictor._save_predictions(predictions_denorm, case_dir)

            # 自动生成张家诚需要的处理后数据（1小时间隔+整点时间）
            try:
                generate_zhangjiacheng_data(case_dir)
                logger.info("已自动生成 zhangjiacheng 文件夹的处理数据")
            except Exception as e:
                logger.error(f"生成 zhangjiacheng 数据失败: {e}")

            # ZIP 在接口中生成，这里返回元信息
            return output_data
        else:
            raise Exception("预测过程未生成任何结果")

    except Exception as e:
        error_details = traceback.format_exc()
        return {
            "error": str(e),
            "details": error_details,
            "status": "failed"
        }


def main1():
    try:
        logger.info("======= 启动天然气管网代理模型 =======")
        # 检查环境
        if not check_environment():
            logger.info("环境检查失败，程序退出")
            exit(1)

        # 使用全局变量，但不在这里声明global
        current_input_data = None
        current_case_dir = None

        # 直接运行预测，自动调用数据转换
        # current_input_data = convert_data_and_get_sample()
        current_case_dir = create_save_directory()
        logger.info(f"预测结果将保存到: {current_case_dir}")

        result = simulate_pipeline_network(current_input_data, current_case_dir)

        if "error" in result:
            logger.info(f"预测失败: {result['error']}")
        else:
            logger.info(f"预测完成!")
            if result.get("terminated_by_step_limit"):
                logger.info("预测已达到步数限制并正常结束")

        logger.info("======= 预测过程结束 =======")
    except KeyboardInterrupt:
        logger.info(f"程序被用户中断")
    except Exception as e:
        logger.info(f"程序执行出错: {str(e)}")
        traceback.print_exc()

if __name__ == "__main__":
    main1()